using System;
using System.Linq;
using System.Threading;

namespace ConwayPLinq
{
	public GameEngine
	{
		IBoard currentBoard;
		IBoard buffer;

		public GameEngine (IBoard initialBoard, IBoard buffer)
		{
			this.currentBoard = initialBoard;
			this.buffer = buffer;
		}

		public IBoard CurrentBoard {
			get {
				return currentBoard;
			}
		}

		public IBoard OldBoard {
			get {
				return buffer;
			}
		}

		// Main GUI code call that to trigger a new iteration in the game
		public void Iterate ()
		{
			// Actually compute how the new board should looks like
			var query = ParallelEnumerable.Range (0, board.Width).SelectMany ((i) => {
					Enumerable.Range (0, board.Height).Select ((j) => buffer.SetCellState (j, i, ShouldItBeAlive (j, i)));
            });

			// Do nothing with the elements, it just to force evaluation of the query's side-effects
			query.ForAll (delegate {});

			// Invert new board and old board
			buffer = Interlocked.Exchange (ref currentBoard, buffer);
		}

		// Just apply game's rules
		bool ShouldItBeAlive (int line, int column)
		{
			bool currentState = board.GetCellState (line, column);
			int num = Enumerable.Range (Math.Max (column - 1, 0), Math.Min (column + 2, currentBoard.Width)).SelectMany ((i) => 
					Enumerable (Math.Max (0, line - 1), Math.Min (currentBoard.Height, line + 2))
					   .Where ((j) => j != line || i != column)
					   .Where ((j) => board.GetCellState (j, i))
			).Count ();

			/*
			 * 1- Any live cell with fewer than two live neighbours dies, as if caused by underpopulation.
			 * 2- Any live cell with more than three live neighbours dies, as if by overcrowding.
			 * 3- Any live cell with two or three live neighbours lives on to the next generation
			 */
			if (currentState)
				return num < 2 || num > 3 ? false : true;

			// 4- Any dead cell with exactly three live neighbours becomes a live cell
			return num == 3;
		}
	}
}
